<?xml version='1.0' encoding='utf-8'?>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Pro Git - 简体中文版
</title>
    <meta content="http://www.w3.org/1999/xhtml; charset=utf-8" http-equiv="Content-Type"/>
    <link href="stylesheet.css" type="text/css" rel="stylesheet"/>
    <style type="text/css">
		@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }</style>
  </head>
  <body class="calibre">
<h2 id="calibre_toc_30" class="calibre3">衍合</h2>

<p class="calibre2">把一个分支整合到另一个分支的办法有两种：<code class="calibre9">merge（合并）</code> 和 <code class="calibre9">rebase（衍合）</code>。在本章我们会学习什么是衍合，如何使用衍合，为什么衍合操作如此富有魅力，以及我们应该在什么情况下使用衍合。</p>

<h3 id="calibre_toc_126" class="calibre4">衍合基础</h3>

<p class="calibre2">请回顾之前有关合并的一节（见图 3-27），你会看到开发进程分叉到两个不同分支，又各自提交了更新。</p>

<p class="calibre2"><img src="18333fig0327-tn.png" alt="图 3-27. 最初分叉的提交历史。" title="图 3-27. 最初分叉的提交历史。" class="calibre5"/></p>

<p class="calibre2">之前介绍过，最容易的整合分支的方法是 <code class="calibre9">merge</code> 命令，它会把两个分支最新的快照（C3 和 C4）以及二者最新的共同祖先（C2）进行三方合并。如图 3-28 所示：</p>

<p class="calibre2"><img src="18333fig0328-tn.png" alt="图 3-28. 通过合并一个分支来整合分叉了的历史。" title="图 3-28. 通过合并一个分支来整合分叉了的历史。" class="calibre5"/></p>

<p class="calibre2">其实，还有另外一个选择：你可以把在 C3 里产生的变化补丁重新在 C4 的基础上打一遍。在 Git 里，这种操作叫做<em class="calibre11">衍合（rebase）</em>。有了 <code class="calibre9">rebase</code> 命令，就可以把在一个分支里提交的改变在另一个分支里重放一遍。</p>

<p class="calibre2">在这个例子里，可以运行下面的命令：</p>

<pre class="calibre8"><code class="calibre9">$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
</code></pre>

<p class="calibre2">它的原理是回到两个分支（你所在的分支和你想要衍合进去的分支）的共同祖先，提取你所在分支每次提交时产生的差异（diff），把这些差异分别保存到临时文件里，然后从当前分支转换到你需要衍合入的分支，依序施用每一个差异补丁文件。图 3-29 演示了这一过程：</p>

<p class="calibre2"><img src="18333fig0329-tn.png" alt="图 3-29. 把 C3 里产生的改变衍合到 C4 中。" title="图 3-29. 把 C3 里产生的改变衍合到 C4 中。" class="calibre5"/></p>

<p class="calibre2">现在，你可以回到 master 分支然后进行一次快进合并（见图 3-30）：</p>

<p class="calibre2"><img src="18333fig0330-tn.png" alt="图 3-30. master 分支的快进。" title="图 3-30. master 分支的快进。" class="calibre5"/></p>

<p class="calibre2">现在，合并后的 C3（即现在的 C3'）所指的快照，同三方合并例子中的 C5 所指的快照内容一模一样了。最后整合得到的结果没有任何区别，但衍合能产生一个更为整洁的提交历史。如果视察一个衍合过的分支的历史记录，看起来更清楚：仿佛所有修改都是先后进行的，尽管实际上它们原来是同时发生的。</p>

<p class="calibre2">你可以经常使用衍合，确保在远程分支里的提交历史更清晰。比方说，某些项目自己不是维护者，但想帮点忙，就应该尽可能使用衍合：先在一个分支里进行开发，当准备向主项目提交补丁的时候，再把它衍合到 <code class="calibre9">origin/master</code> 里面。这样，维护者就不需要做任何整合工作，只需根据你提供的仓库地址作一次快进，或者采纳你提交的补丁。</p>

<p class="calibre2">请注意，合并结果中最后一次提交所指向的快照，无论是通过一次衍合还是一次三方合并，都是同样的快照内容，只是提交的历史不同罢了。衍合按照每行改变发生的次序重演发生的改变，而合并是把最终结果合在一起。</p>

<h3 id="calibre_toc_127" class="calibre4">更多有趣的衍合</h3>

<p class="calibre2">你还可以在衍合分支以外的地方衍合。以图 3-31 的历史为例。你创建了一个特性分支 <code class="calibre9">server</code> 来给服务器端代码添加一些功能，然后提交 C3 和 C4。然后从 C3 的地方再增加一个 <code class="calibre9">client</code> 分支来对客户端代码进行一些修改，提交 C8 和 C9。最后，又回到 <code class="calibre9">server</code> 分支提交了 C10。</p>

<p class="calibre2"><img src="18333fig0331-tn.png" alt="图 3-31. 从一个特性分支里再分出一个特性分支的历史。" title="图 3-31. 从一个特性分支里再分出一个特性分支的历史。" class="calibre5"/></p>

<p class="calibre2">假设在接下来的一次软件发布中，你决定把客户端的修改先合并到主线中，而暂缓并入服务端软件的修改（因为还需要进一步测试）。你可以仅提取对客户端的改变（C8 和 C9），然后通过使用 <code class="calibre9">git rebase</code> 的 <code class="calibre9">--onto</code> 选项来把它们在 master 分支上重演：</p>

<pre class="calibre8"><code class="calibre9">$ git rebase --onto master server client
</code></pre>

<p class="calibre2">这基本上等于在说“检出 client 分支，找出 <code class="calibre9">client</code> 分支和 <code class="calibre9">server</code> 分支的共同祖先之后的变化，然后把它们在 <code class="calibre9">master</code> 上重演一遍”。是不是有点复杂？不过它的结果，如图 3-32 所示，非常酷：</p>

<p class="calibre2"><img src="18333fig0332-tn.png" alt="图 3-32. 衍合一个特性分支上的另一个特性分支。" title="图 3-32. 衍合一个特性分支上的另一个特性分支。" class="calibre5"/></p>

<p class="calibre2">现在可以快进 master 分支了（见图 3-33）：</p>

<pre class="calibre8"><code class="calibre9">$ git checkout master
$ git merge client
</code></pre>

<p class="calibre2"><img src="18333fig0333-tn.png" alt="图 3-33. 快进 master 分支，使之包含 client 分支的变化。" title="图 3-33. 快进 master 分支，使之包含 client 分支的变化。" class="calibre5"/></p>

<p class="calibre2">现在你决定把 <code class="calibre9">server</code> 分支的变化也包含进来。可以直接把 <code class="calibre9">server</code> 分支衍合到 <code class="calibre9">master</code> 而不用手工转到 <code class="calibre9">server</code> 分支再衍合。<code class="calibre9">git rebase [主分支] [特性分支]</code> 命令会先检出特性分支 <code class="calibre9">server</code>，然后在主分支 <code class="calibre9">master</code> 上重演：</p>

<pre class="calibre8"><code class="calibre9">$ git rebase master server
</code></pre>

<p class="calibre2">于是 <code class="calibre9">server</code> 的进度应用到 <code class="calibre9">master</code> 的基础上，如图 3-34：</p>

<p class="calibre2"><img src="18333fig0334-tn.png" alt="图 3-34. 在 master 分支上衍合 server 分支。" title="图 3-34. 在 master 分支上衍合 server 分支。" class="calibre5"/></p>

<p class="calibre2">然后快进主分支 <code class="calibre9">master</code>：</p>

<pre class="calibre8"><code class="calibre9">$ git checkout master
$ git merge server
</code></pre>

<p class="calibre2">现在 <code class="calibre9">client</code> 和 <code class="calibre9">server</code> 分支的变化都被整合了，不妨删掉它们，把你的提交历史变成图 3-35 的样子：</p>

<pre class="calibre8"><code class="calibre9">$ git branch -d client
$ git branch -d server
</code></pre>

<p class="calibre2"><img src="18333fig0335-tn.png" alt="图 3-35. 最终的提交历史" title="图 3-35. 最终的提交历史" class="calibre5"/></p>

<h3 id="calibre_toc_128" class="calibre4">衍合的风险</h3>

<p class="calibre2">呃，奇妙的衍合也不是完美无缺的，一句话可以总结这点：</p>

<p class="calibre2"><strong class="calibre12">永远不要衍合那些已经推送到公共仓库的更新。</strong></p>

<p class="calibre2">如果你遵循这条金科玉律，就不会出差错。否则，人民群众会仇恨你，你的朋友和家人也会嘲笑你，唾弃你。</p>

<p class="calibre2">在衍合的时候，实际上抛弃了一些现存的 commit 而创造了一些类似但不同的新 commit。如果你把commit 推送到某处然后其他人下载并在其基础上工作，然后你用 <code class="calibre9">git rebase</code> 重写了这些commit 再推送一次，你的合作者就不得不重新合并他们的工作，这样当你再次从他们那里获取内容的时候事情就会变得一团糟。</p>

<p class="calibre2">下面我们用一个实际例子来说明为什么公开的衍合会带来问题。假设你从一个中央服务器克隆然后在它的基础上搞了一些开发，提交历史类似图 3-36：</p>

<p class="calibre2"><img src="18333fig0336-tn.png" alt="图 3-36. 克隆一个仓库，在其基础上工作一番。" title="图 3-36. 克隆一个仓库，在其基础上工作一番。" class="calibre5"/></p>

<p class="calibre2">现在，其他人进行了一些包含一次合并的工作（得到结果 C6），然后把它推送到了中央服务器。你获取了这些数据并把它们合并到你本地的开发进程里，让你的历史变成类似图 3-37 这样：</p>

<p class="calibre2"><img src="18333fig0337-tn.png" alt="图 3-37. 获取更多提交，并入你的开发进程。" title="图 3-37. 获取更多提交，并入你的开发进程。" class="calibre5"/></p>

<p class="calibre2">接下来，那个推送 C6 上来的人决定用衍合取代那次合并；他们用 <code class="calibre9">git push --force</code> 覆盖了服务器上的历史，得到 C4'。然后你再从服务器上获取更新：</p>

<p class="calibre2"><img src="18333fig0338-tn.png" alt="图 3-38. 有人推送了衍合过的 C4'，丢弃了你作为开发基础的 C6。" title="图 3-38. 有人推送了衍合过的 C4'，丢弃了你作为开发基础的 C6。" class="calibre5"/></p>

<p class="calibre2">这时候，你需要再次合并这些内容，尽管之前已经做过一次了。衍合会改变这些 commit 的 SHA-1 校验值，这样 Git 会把它们当作新的 commit，然而这时候在你的提交历史早就有了 C4 的内容（见图 3-39）:</p>

<p class="calibre2"><img src="18333fig0339-tn.png" alt="图 3-39. 你把相同的内容又合并了一遍，生成一个新的提交 C8。" title="图 3-39. 你把相同的内容又合并了一遍，生成一个新的提交 C8。" class="calibre5"/></p>

<p class="calibre2">你迟早都是要并入其他协作者提交的内容的，这样才能保持同步。当你做完这些，你的提交历史里会同时包含 C4 和 C4'，两者有着不同的 SHA-1 校验值，但却拥有一样的作者日期与提交说明，令人费解！更糟糕的是，当你把这样的历史推送到服务器，会再次把这些衍合的提交引入到中央服务器，进一步迷惑其他人。</p>

<p class="calibre2">如果把衍合当成一种在推送之前清理提交历史的手段，而且仅仅衍合那些永远不会公开的 commit，那就不会有任何问题。如果衍合那些已经公开的 commit，而与此同时其他人已经用这些 commit 进行了后续的开发工作，那你有得麻烦了。</p>

</body>
</html>
